package cn.cool.middleware.biz_exception_retry.aspect;

import cn.cool.middleware.biz_exception_retry.config.BizExceptionRetryConfig;
import cn.cool.middleware.biz_exception_retry.config.interfaces.BizExceptionRetryStrategy;
import cn.cool.middleware.biz_exception_retry.config.registry.BeanMethodRegistry;
import cn.cool.middleware.biz_exception_retry.domain.IBizExceptionRetryRecordService;
import cn.cool.middleware.biz_exception_retry.domain.model.entity.BizExceptionRetryParamEntity;
import cn.cool.middleware.biz_exception_retry.domain.model.entity.BizExceptionRetryRecordEntity;
import cn.cool.middleware.biz_exception_retry.types.common.BizExceptionRetryConstantsVO;
import cn.cool.middleware.biz_exception_retry.types.annotation.BizExceptionRetry;
import cn.cool.middleware.biz_exception_retry.types.common.BizExceptionRetryRecordStatusVO;
import cn.cool.middleware.biz_exception_retry.types.common.context.RetryContext;
import cn.cool.middleware.biz_exception_retry.types.common.helper.BizMemoryRetryHelper;
import cn.cool.middleware.biz_exception_retry.types.exception.BizExceptionRetryException;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.ApplicationContext;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

@Slf4j
@Aspect
public class BizExceptionRetryAspect {
    @Resource
    private BizExceptionRetryConfig config;

    @Resource
    private IBizExceptionRetryRecordService bizExceptionRetryRecordService;

    @Resource
    private ApplicationContext applicationContext;

    @Resource
    private BeanMethodRegistry beanMethodRegistry;

    @Around(value = "@annotation(bizExceptionRetry)")
    public Object bizExceptionHandler(ProceedingJoinPoint joinPoint, BizExceptionRetry bizExceptionRetry) {
        // 1. 前置参数校验
        this.beforeCheck(bizExceptionRetry);
        try {
            // 2. 目标方法执行
            return joinPoint.proceed();
        } catch (Throwable ex) {
            // 3. 如果发生异常，就走异常重试逻辑
            if (!RetryContext.get()) {
                // 3.1. 内存重试核心方法（内存重试 & 入库）
                return this.handleBizExceptionRetry(joinPoint, bizExceptionRetry, ex);
            }
            // 3.2. 如果是走的自定义重试链路，执行目标方法错误后，那么就直接抛出错误
            throw new BizExceptionRetryException(String.format("[@BizExceptionRetry] 业务异常重试失败，record = %s", JSONUtil.toJsonStr(bizExceptionRetry)), ex);
        }
    }

    /**
     * Around 前置通知中，在目标方法执行前进行进行参数校验
     */
    private void beforeCheck(BizExceptionRetry bizExceptionRetry) {
        // 检查 BizExceptionRetry 是否为空
        if (bizExceptionRetry == null) {
            throw new BizExceptionRetryException("[@BizExceptionRetry] bizExceptionRetry annotation cannot be null");
        }

        // 业务异常类型非空 or 长度限制 check
        if (StrUtil.isBlank(bizExceptionRetry.exceptionType()) || bizExceptionRetry.exceptionType().length() > BizExceptionRetryConstantsVO.BIZ_EXCEPTION_RETRY_EXCEPTION_TYPE.getLength()) {
            throw new BizExceptionRetryException("[@BizExceptionRetry] exceptionType cannot be blank or length over 128");
        }

        // 异常信息描述
        if (StrUtil.isBlank(bizExceptionRetry.exceptionDesc())) {
            log.warn("[@BizExceptionRetry] exceptionDesc is blank, defaulting to 'unknown exception'");
        }

        // 模块名字
        if (StrUtil.isBlank(config.getExceptionModule())) {
            throw new BizExceptionRetryException("[@BizExceptionRetry] exceptionModule cannot be blank");
        }

        // 当前 retryBeanName 是否存在，如果 retryBeanName不为空，那么是否在当前ioc容器中存在该 bean
        String retryBeanName = bizExceptionRetry.retryBeanName();
        if (StrUtil.isNotBlank(retryBeanName)) {
            try {
                applicationContext.getBean(retryBeanName, BizExceptionRetryStrategy.class);
            } catch (Exception e) {
                log.error("[@BizExceptionRetry] Failed to get bean: " + retryBeanName, e);
                throw new BizExceptionRetryException("[@BizExceptionRetry] Failed to get bean", e);
            }
        }
    }

    /**
     * 处理异常重试逻辑
     * @param joinPoint 连接点
     * @param bizExceptionRetry 业务异常重试注解信息
     * @param ex 目标方法抛出的异常对象
     */
    private Object handleBizExceptionRetry(ProceedingJoinPoint joinPoint, BizExceptionRetry bizExceptionRetry, Throwable ex) {
        String exClassName = ex.getClass().getName();
        // 1. 是否排除了当前异常类型
        if (this.isExclude(bizExceptionRetry, ex)) {
            log.info("[@BizExceptionRetry] exception class: {} is excluded, no need retry", exClassName, ex);
            return null;
        }

        // 2. 是否包含了当前异常
        if (this.isInclude(bizExceptionRetry, ex)) {
            log.info("[@BizExceptionRetry] exception class: {} is included, need retry", exClassName, ex);

            boolean enableMemoryRetry = bizExceptionRetry.enableMemoryRetry();
            int memoryRetryMaxNum = this.getMemoryRetryMaxNum(bizExceptionRetry);
            long memoryRetryIntervalMillis = this.getMemoryRetryIntervalMillis(bizExceptionRetry);

            // 3. 尝试内存重试
            if (enableMemoryRetry && memoryRetryMaxNum > 0) {
                String className = joinPoint.getTarget().getClass().getName();
                String methodName = joinPoint.getSignature().getName();
                log.info("[@BizExceptionRetry] class: {}, method: {} start memory retry, retry counter: {}, retry interval: {}", className, methodName, memoryRetryMaxNum, memoryRetryIntervalMillis);

                try {
                    // 4. 开始内存重试
                    BizMemoryRetryHelper.retry(() -> {
                        try {
                            return joinPoint.proceed();
                        } catch (Throwable e) {
                            throw new BizExceptionRetryException(e.getMessage(), e);
                        }
                    }, memoryRetryMaxNum, memoryRetryIntervalMillis);
                } catch (Throwable memoryRetryEx) {
                    // 5. 内存重试超过最大内存重试次数时，持久化异常信息，后续定时任务进行重试
                    this.exceptionSave(joinPoint, bizExceptionRetry, memoryRetryEx);
                    throw new BizExceptionRetryException("Memory retry failed, task saved for persistence retry", memoryRetryEx);
                }
            } else {
                // 未开启内存重试，持久化异常信息，后续定时任务进行重试
                this.exceptionSave(joinPoint, bizExceptionRetry, ex);
                throw new BizExceptionRetryException("[@BizExceptionRetry] Memory retry failed, task saved for persistence retry", ex);
            }
        } else {
            throw new BizExceptionRetryException("[@BizExceptionRetry] No memory retry configured, task saved for persistence retry", ex);
        }

        return null;
    }

    /**
     * 抛出的异常是否需要被排除 exclude ？
     */
    private boolean isExclude(BizExceptionRetry bizExceptionRetry, Throwable ex) {
        for (Class<? extends Throwable> exClazz : bizExceptionRetry.exception().excludes()) {
            if (exClazz.isAssignableFrom(ex.getClass())) {
                return true;
            }
        }
        return false;
    }

    /**
     * 抛出的异常是否需要被捕获 include ？
     */
    private boolean isInclude(BizExceptionRetry bizExceptionRetry, Throwable ex) {
        // 如果 {include} 为空，那么就需要拦截所有的异常信息
        if (ArrayUtil.isEmpty(bizExceptionRetry.exception().includes())) {
            return true;
        }
        // 遍历 includes
        for (Class<? extends Throwable> exClazz : bizExceptionRetry.exception().includes()) {
            if (exClazz.isAssignableFrom(ex.getClass())) {
                return true;
            }
        }

        // 遍历 value()
        for (Class<? extends Throwable> exClazz : bizExceptionRetry.exception().value()) {
            if (exClazz.isAssignableFrom(ex.getClass())) {
                return true;
            }
        }

        return false;
    }

    /**
     * 重试最大次数，在[0, 6]之间
     */
    private int getMemoryRetryMaxNum(BizExceptionRetry bizExceptionRetry) {
        int memoryRetryMaxNum = bizExceptionRetry.memoryRetryMaxNum();
        memoryRetryMaxNum = Math.max(memoryRetryMaxNum, 0);
        return Math.min(memoryRetryMaxNum, 6);
    }

    /**
     * 重试时间间隔，intervalMillis >= 0
     * 单位：毫秒
     */
    private long getMemoryRetryIntervalMillis(BizExceptionRetry bizExceptionRetry) {
        long intervalMillis = bizExceptionRetry.memoryRetryIntervalMillis();
        return intervalMillis < 0 ? 0 : intervalMillis;
    }

    /**
     * 保存业务异常重试信息到重试表 record
     */
    private void exceptionSave(ProceedingJoinPoint joinPoint, BizExceptionRetry bizExceptionRetry, Throwable e) {
        BizExceptionRetryRecordEntity bizExceptionRetryRecordEntity = buildBizExceptionRetryRecordEntity(joinPoint, bizExceptionRetry, e);
        bizExceptionRetryRecordService.saveBizExceptionRetryRecord(bizExceptionRetryRecordEntity);
    }

    /**
     * 构建业务异常重试记录 record 对象
     */
    private BizExceptionRetryRecordEntity buildBizExceptionRetryRecordEntity(ProceedingJoinPoint joinPoint, BizExceptionRetry bizExceptionRetry, Throwable ex) {
        BizExceptionRetryParamEntity retryParam = buildBizExceptionRetryParamEntity(joinPoint, bizExceptionRetry);

        // 构建异常信息描述
        Map<String, String> exceptionMessage = new HashMap<>();
        exceptionMessage.put("reqId", UUID.randomUUID().toString(true)); // 可替换为真实请求链路ID
        exceptionMessage.put("exception_desc", truncate(bizExceptionRetry.exceptionDesc(), BizExceptionRetryConstantsVO.BIZ_EXCEPTION_RETRY_EXCEPTION_DESC.getLength()));
        if (ex != null && StrUtil.isNotBlank(ex.getMessage())) {
            exceptionMessage.put("ex_message", ex.getMessage());
        }

        return BizExceptionRetryRecordEntity.builder()
                .exceptionType(retryParam.getExceptionType())
                .exceptionMessage(JSONUtil.toJsonStr(exceptionMessage))
                .exceptionModule(retryParam.getExceptionModule())
                .className(retryParam.getClassName())
                .methodSignature(retryParam.getMethodSignature())
                .retryParam(JSONUtil.toJsonStr(retryParam))
                .retryBeanName(retryParam.getRetryBeanName())
                .objectId(truncate(retryParam.getObjectId(), BizExceptionRetryConstantsVO.BIZ_EXCEPTION_RETRY_OBJECT_ID.getLength()))
                .dealNum(0)
                .maxNum(Math.max(0, bizExceptionRetry.maxRetryNum()))
                .dealStatus(BizExceptionRetryRecordStatusVO.UN_DEAL.getStatus())
                .build();
    }

    /**
     * 构建重试参数对象
     */
    public BizExceptionRetryParamEntity buildBizExceptionRetryParamEntity(ProceedingJoinPoint joinPoint, BizExceptionRetry bizExceptionRetry) {
        String objectId = resolveObjectId(joinPoint, bizExceptionRetry.objectIdExpr());

        Method targetMethod = ((MethodSignature) joinPoint.getSignature()).getMethod();

        return BizExceptionRetryParamEntity.builder()
                .exceptionType(bizExceptionRetry.exceptionType())
                .exceptionMessage(bizExceptionRetry.exceptionDesc())
                .exceptionModule(config.getExceptionModule())
                .retryBeanName(bizExceptionRetry.retryBeanName())
                .objectId(objectId)
                .className(joinPoint.getTarget().getClass().getName())
                .methodSignature(beanMethodRegistry.createMethodSignature(targetMethod))
                .retryParam(joinPoint.getArgs())
                .build();
    }

    /**
     * 截断字符串至指定长度
     */
    private String truncate(String value, int maxLength) {
        if (StrUtil.isBlank(value)) return "";
        return value.length() > maxLength ? value.substring(0, maxLength) : value;
    }

    /**
     * 解析 objectId 表达式，如 "$0.id"
     */
    private String resolveObjectId(ProceedingJoinPoint joinPoint, String objectIdExpr) {
        try {
            return ""; // todo
        } catch (Exception e) {
            log.warn("Failed to resolve objectIdExpr: {}", objectIdExpr, e);
            return "";
        }
    }
}